% DEFINE THE LOGIC BASE CLASS
% The logic base class
classdef Logic < handle
 
  % DEFINE PROPERTIES
  properties (SetAccess = public)
    Name = '';
    Modules = struct('Name',{},'Type',{},'ID',{},'IDType',{});
    State = '';
    Events = struct([]); % Holds events that occur during processing
    Trial = 0; % Trial in the current class
    TrialActive = 0;
    TimeToContinue = 0;
    ParadigmActive = 0;
    ParametersFull = []; 
    Parameters = [];
    HW = struct('NInputs',0,'NOutputs',0,'NDOutputs',0);
    Display = []; % Contains all information relating to the display of the object
    Performance = []; % Contrains all information relating to the animal performance over different trials
    Trials = [];
    Timers = [];
    StartTime = [];
    States = []; % States, such as InputStates, Animalposition, etc.
  end
  
  % DEFINE EVENTS
  events
    
  end
  
  % DEFINE METHODS 
  methods
    % CONSTRUCTOR
    function O = Logic(varargin);
      O.changeState('Ready');
    end
    
    % START PARADIGM
    function start(O)
      global CG;
      O.ParadigmActive = 1;
      O.TrialActive = 0;
      O.VideoActive = 0;
      O.VideoReady = 0;
      O.deleteTimers;
      O.createMemoryTimer;
      O.setTrial(0);      
      O.Trials = [];
      O.Performance = [];
      O.StartTime = datevec(now);
      set(CG.GUI.Main.BackupButton,'Enable','off');
      CG.Files.RecordingPath = [CG.Files.AnimalPath,'R',num2str(CG.Parameters.General.Recording),filesep];
      CG.Files.RecordingPathTmp = [CG.Files.AnimalPathTmp,'R',num2str(CG.Parameters.General.Recording),filesep];
      C_generateIdentifier;
      % Close all parameter figures of the modules
      for iM=1:length(O.Modules)
        if ishandle(O.Modules(iM).ParFIG)
          close(O.Modules(iM).ParFIG);
        else
          eval(['C_prepare',O.Modules(iM).Type,'(',n2s(O.Modules(iM).IDType),');']);
        end
      end
      O.changeState('Started');
    end
    
    % STOP THE PARADIGM
    function stop(O) 
      global CG;
 
      for iM=1:length(O.Modules)
        eval(['C_stop',O.Modules(iM).Type,'(',n2s(O.Modules(iM).IDType),');']);
      end

      % WRITE INFORMATION TO DATA BASE
      O.writeToDB('Complete',1);

      disp('Starting Copy to E Drive');
      % MOVE DATA FROM TMP LOCATION TO SAVING LOCATION
      [Status,Message] = copyfile(CG.Files.RecordingPathTmp,CG.Files.RecordingPath);
      if Status; % COPY SUCCEEDED
        try 
          rmdir(CG.Files.RecordingPathTmp,'s');
        catch
          disp(['Warning : Folder ',escapeMasker(CG.Files.RecordingPathTmp),' could not be removed.']);
        end
      else
        disp(Message);
        keyboard;
      end
      disp('Stopping Copy to E Drive');
      
      O.nextRecording;
      O.ParadigmActive = 0;
      try close(O.Display.Handles.FIG); end
      set(CG.GUI.Main.BackupButton,'Enable','on');
      disp('[ Paradigm stopped ]');
    end

    % ASSIGN PARAMETERS TO THEIR USUAL LOCATIONS
    function assignParameters(O)
      global CG;
      for i=1:size(O.ParametersFull,1)
        O.Parameters.(O.ParametersFull{i,1}) = O.ParametersFull{i,2};
      end
    end
    
    % STEP TO NEXT RECORDING
    function nextRecording(O)
      global CG;
      CG.Parameters.General.Recording = CG.Parameters.General.Recording + 1;
      set(CG.GUI.Main.RecordingSelection,'String',num2str(CG.Parameters.General.Recording));
    end

    function prepareTrial(O)
      O.increaseTrial;
      O.prepareFiles;
    end
    
    function startTrial(O)
      O.TrialActive = 1; 
      O.changeState('TrialActive');
    end

    function stopTrial(O)
      O.TrialActive = 0;
      O.saveData;
      C_setDiskspace;
      O.changeState('Waiting');
      O.writeToDB;
    end
    
    function writeToDB(O,varargin)
      global CG;
      disp('Starting Write to DB');
      if C_checkDatabase
        P = parsePairs(varargin);
        checkField(P,'Complete',0);
        
        % ADD THE CURRENT RECORDING TO THE DATA BASE
        if ~P.Complete
          switch O.Trial
            case 1;  SQL = ['INSERT INTO recordings SET ',...
                'animal=''',CG.Parameters.General.Animal,''', '...
                'recording=',num2str(CG.Parameters.General.Recording),', '...
                'user=''',CG.Parameters.General.User,''', '...
                'paradigm=''',O.Name,''', '...
                'lab=''',CG.Parameters.General.Lab,''', '...
                'setup=''',CG.Parameters.General.Setup,''', '...
                'hostname=''',HF_getHostname,''', '...
                'date=''',datestr(O.StartTime,31),''', '...
                'trials=',num2str(O.Trial),', '...
                'bad=0, '...
                'onserver=0, '...
                'complete=0'];
            case 0; SQL = '';
            otherwise; SQL = ['UPDATE recordings SET ',...
                ' trials=',num2str(O.Trial),', '...
                ' complete=',num2str(P.Complete),...
                ' WHERE id=',num2str(CG.Parameters.General.RecID)];
          end
        else
          SQL = ['UPDATE recordings SET ',...
            ' trials=',num2str(O.Trial),', '...
            ' complete=1 ',...
            ' WHERE id=',num2str(CG.Parameters.General.RecID)];
        end
        if ~isempty(SQL) [R,A,ID] = mysql(SQL); end
        if O.Trial == 1
          CG.Parameters.General.RecID = ID;
        end
        
      end
      disp('Stopping Write To DB');
    end
        
     % START THE ACQUISITION OF SENSOR DATA
    function startAcquisition(~,ID)
      C_startNI(ID);
    end
    
    % UPDATE STATES OF SENSORS
    function [InputStates,InputStatesAnalog] = updateStates(O,Time)
      global CG
      if nargin<2 Time = now*CG.Misc.DateNum2SecFactor; end
      ID = 1;
      if ~isempty(CG.Data.NI(ID).Time)
        PacketsAcquired = CG.Sessions.NI(ID).PacketsAcquired;
        cInd = find(Time>=CG.Data.NI(ID).Time(PacketsAcquired:-1:1,1),1,'first');
        SelectedPos = PacketsAcquired-cInd+1;
        InputStatesAnalog = CG.Data.NI(ID).Analog(SelectedPos,:);
        InputStates = InputStatesAnalog>CG.Sessions.NI(ID).AnalogThresholds;
      else
        InputStates = zeros(1, length(O.HW.Inputs));
      end
      O.States.InputStates = InputStates;
    end
    
    % CLOSE MODULES
    % Problematic for Video on the Mac, leads to a crash when deleting the video object
    function close(O)
      for iM=1:length(O.Modules)
        eval(['C_close',O.Modules(iM).Type,'(',n2s(O.Modules(iM).IDType),');']);
      end
    end

    % START TRIAL TIMER
    function createTrialTimer(O,DelayTime)
      O.deleteTrialTimer;
      if ~exist('DelayTime','var') DelayTime = O.Parameters.TrialDurationMax; end
      if DelayTime<inf
        O.Parameters.TrialDurationMax;
        Callback = ['fprintf(''=== Timer for Trial has expired (',num2str(DelayTime),'s) =>  NewTrial. ====\n'');'...
          ' global CG; '...
          'CG.Paradigm.TrialActive = 0;'...  
          'C_setDigitalNI(CG.Paradigm.HW.IDs.DAQ,''Trial'',0); '...
          'CG.Paradigm.stopTrial; '...
          'C_setDigitalNI(CG.Paradigm.HW.IDs.DAQ,''Camera'',1); '... % FREE ARDUINO CAM           
          'C_setDigitalNI(CG.Paradigm.HW.IDs.DAQ,''Camera'',0); '...
          'flushdata(CG.Sessions.Video(1).S); ',...
          'CG.Paradigm.prepareTrial; '...
          'pauseUntil(CG.Paradigm.TimeToContinue); '...
          'CG.Paradigm.startTrial;'];
        O.Timers.Trial = timer('TimerFcn',Callback,...
          'StartDelay',DelayTime,'Name','TrialDurationMax','BusyMode','queue');
        O.Timers.TrialTimer = timer('TimerFcn','global CG; start(CG.Paradigm.Timers.Trial)','BusyMode','queue','StartDelay',2);
      end      
    end
    
    % DELETE/STOP TRIAL TIMER;
    function deleteTrialTimer(O)
      if O.Parameters.TrialDurationMax<inf
        Timers = timerfind;
        for i=1:length(Timers)
          if isvalid(Timers(i)) && strcmp(Timers(i).Name,'TrialDurationMax')
            stop(Timers(i)); delete(Timers(i)); O.Timers.Trial = [];
          end;
        end
      end
    end
  
    % CREATE THE MEMORY TIMER 
    function createMemoryTimer(O)
      O.deleteMemoryTimer;
      O.createTrialTimer(0.1);
      Callback = [...  
        'global CG; Limit = 0.4;'...
        'MemFull = CG.Sessions.Video(1).S.FramesAvailable/CG.Sessions.Video(1).FramesMemoryPhys; '...
        'disp([''Memory taken : '',num2str(MemFull),''. Stopping at '',num2str(Limit),''.'']);'...
        'if  MemFull > Limit; '...
        '   disp(''Running Low Memory Timer!'');'...  
        '   CG.Paradigm.runTrialTimer; '...
        'end'];
      O.Timers.LowMemory = timer('TimerFcn',Callback,...
        'ExecutionMode','fixedRate','Period',2,'Name','LowMemoryTimer','BusyMode','drop');
    end
    
    % DELETE THE MEMORY TIMER
    function deleteMemoryTimer(O)
      Timers = timerfind;
      for i=1:length(Timers)
        if isvalid(Timers(i)) && strcmp(Timers(i).Name,'LowMemoryTimer')
          stop(Timers(i)); delete(Timers(i)); O.Timers.LowMemory = [];
        end;
      end
    end
   
    % DELETE THE MEMORY TIMER
    function runTrialTimer(O)
      if ~isempty(O.Timers) && isfield(O.Timers,'Trial')
        stop(O.Timers.Trial);
        set(O.Timers.Trial,'StartDelay',0.1);
        start(O.Timers.Trial);
      end
    end
   
    
    % DELETE/STOP TRIAL TIMER;
    function stopTrialTimer(O)
      if ~isempty(O.Timers) && isfield(O.Timers,'Trial')
        stop(O.Timers.Trial);
        disp('Stopping Trial Timer');
      end
    end
    
    function startTrialTimer(O)
      if ~isempty(O.Timers) && isfield(O.Timers,'Trial')
        start(O.Timers.TrialTimer);
        disp('Starting Trial Timer');
      end
    end
    
    
    % DELETE ALL TIMERS
    function deleteTimers(O)
      global CG;
      Timers = timerfind;
      for i=1:length(Timers);
        if isvalid(Timers(i)); stop(Timers(i)); delete(Timers(i));
        end;
      end
    end
    
    
    
    % EVENT PROCESSING
    function O = processEvent(O,SourceName,Event);
      switch Event.Name
        case 'Key_v';
          C_startVideo;
        case 'Key_V';
          C_stopVideo; C_prepareVideo;
          O.increaseTrial;
        case 'Key_a';
          C_startNI;
        case 'Key_A';
          C_stopNI;
          O.increaseTrial;
        case 'Key_l';
          C_sendMessageArduino(1,'movemotor',{1,1,500});
        case 'Key_r'
          C_sendMessageArduino(1,'movemotor',{1,0,500});
        case 'Key_L';
          C_sendMessageArduino(2,'movemotor',{2,1,500});
        case 'Key_R'
          C_sendMessageArduino(2,'movemotor',{2,0,500});
        case 'Key_u';
          C_sendMessageArduino(1,'moveservo',{1,10,20});
        case 'Key_d'
          C_sendMessageArduino(1,'moveservo',{1,10,20});
      end

    end
    
    % SHOW/HIDE GUI
    function O = showDisplay(O,State)
      global CG;
      if isempty( CG.Display.Paradigm.FIG) | ~ishandle(CG.Display.Paradigm.FIG)
        O.prepareDisplay;
      end
      set(CG.GUI.Main.Paradigm.ShowButton,'Value',State);
      DeleteFcn = ['set(CG.GUI.Main.Paradigm.ShowButton,''Value'',0);'];
      set(O.Display.Handles.FIG,'Visible',CG.Misc.StateStrings{State+1},'DeleteFcn',DeleteFcn);
    end
    
    % UPDATE DISPLAY
    function O = updateDisplay(O)
    end
    
    % STATE OF OBJECT CHANGES
    function changeState(O,NewState)
      global CG;
      if ~strcmp(NewState,O.State)
        O.State = NewState;
        try set(CG.GUI.Main.State,'String',O.State); end
      end
    end
    
    % PREPARE PATHS/FILES FOR SAVING DATA
    function prepareFiles(O)
      %    Save Format
      %    One Directory per Recording
      %    Animal/Recording/{ModuleTypes}
      %    M1/R123/General.mat % Events and other things
      %    M1/R123/Video/
      %    M1/R123/AudioIn/
      %    M1/R123/AudioOut/
      %    M1/R123/EPhys/
      %    M1/R123/Arduino/
      %    M1/R123_{Paradigm}_{Date}.txt % File for easy access to Paradigm, Recording Time
      
      global CG;
      Sep = filesep;
      % PREPARE DIRECTORIES
      if O.Trial == 1
        % CHECK MAIN DIRECTORY
        if ~isdir(CG.Files.RecordingPathTmp) mkdirAll(CG.Files.RecordingPathTmp); end
        
        % MAKE DIRECTORIES FOR MODULES
        for iM = 1:length(O.Modules)
          cName = O.Modules(iM).Name;
          cType = O.Modules(iM).Type;
          cIDType = O.Modules(iM).IDType;
          cPath = [CG.Files.RecordingPath,cName,Sep];
          CG.Files.(cType)(cIDType).DataPath = cPath;
          cPath = [CG.Files.RecordingPathTmp,cName,Sep];
          CG.Files.(cType)(cIDType).DataPathTmp = cPath;
          if ~isdir(cPath) mkdirAll(cPath); end
        end
        
        cDate = datestr(now,'dd-mm-yyyy_HH-MM-SS');
        CG.Files.InfoFileName = [CG.Files.RecordingPathTmp,...
          CG.Parameters.General.Identifier,'_',O.Name,'_',cDate,'.txt'];
        FID = fopen(CG.Files.InfoFileName,'w'); fclose(FID);
      end
        
      % PREPARE FILES FOR EACH TRIAL
      for iM = 1:length(O.Modules)
        cName = O.Modules(iM).Name;
        cType = O.Modules(iM).Type;
        cIDType = O.Modules(iM).IDType;
        CG.Files.(cType)(cIDType).FileName = [CG.Files.(cType)(cIDType).DataPath,'Data_',num2str(O.Trial),'.mat'];
        CG.Files.(cType)(cIDType).FileNameTmp = [CG.Files.(cType)(cIDType).DataPathTmp,'Data_',num2str(O.Trial),'.mat'];
      end
      
      % GENERAL INFORMATION FILE 
      CG.Files.General.FileName = [ CG.Files.RecordingPath,'General.mat'];
      CG.Files.General.FileNameTmp = [ CG.Files.RecordingPathTmp,'General.mat'];
    end
    
    % SAVE DATA OF CURRENT STATE
    function saveData(O,Trials)      
      global CG;
      
      % SAVE GENERAL INFORMATION (INCLUDES PARADIGM)
      % take out sessions, display, gui, devices, colors
      CGSave = rmfield(CG,{'Display','GUI','Devices','Colors','Data'});
      FN =fieldnames(CGSave.Sessions);
      for iM=1:length(FN);
        cModule = FN{iM};
        try ; CGSave.Sessions.(cModule) = rmfield(CGSave.Sessions.(cModule),'S'); end
        try ; CGSave.Sessions.(cModule) = rmfield(CGSave.Sessions.(cModule),'SAI'); end
        try ; CGSave.Sessions.(cModule) = rmfield(CGSave.Sessions.(cModule),'SD'); end
        try ; CGSave.Sessions.(cModule) = rmfield(CGSave.Sessions.(cModule),'SAO'); end
        try ; CGSave.Sessions.(cModule) = rmfield(CGSave.Sessions.(cModule),'Source'); end
        try ; CGSave.Sessions.(cModule) = rmfield(CGSave.Sessions.(cModule),'SourceInstant'); end
        try ; CGSave.Sessions.(cModule) = rmfield(CGSave.Sessions.(cModule),'CallbackAvail'); end
      end
      save(CG.Files.General.FileNameTmp,'CGSave');      
      
      % Detailed Info:      
      % NI : Save Data with Timing
      % Camera : Save Frames with Timing
      % AudioIn : Save Recorded Audio
      % AudioOut : Do nothing
      try 
      % Question here: how to best reset the modules.
      for iM = 1:length(O.Modules)
        cName = O.Modules(iM).Name;
        cIDType = O.Modules(iM).IDType;
        eval(['C_save',O.Modules(iM).Type,'(cIDType)']);
      end
      disp('All Data Saved.')
      catch Exception
        rethrow(Exception);
        keyboard
      end
    end
    
    % PREPARE FIGURE
    function prepareDisplay(O)
      global CG;
       
      % PREPARE FIGURE FOR PLOTTING THE STATE
      cFIG = 10^8; O.Display.Handles.FIG = cFIG;
      figure(cFIG); if O.Trial==0 clf; end;
      StartXPos = CG.GUI.LastXPos + 10; Width = 250; Height = 300;
      SS = get(0,'ScreenSize');
      if StartXPos+Width > SS(3) StartXPos = CG.GUI.Main.Width + 10;  end
      set(cFIG,'Visible','off','NumberTitle','off','Name',['Output for Paradigm'],...
      'MenuBar','none','Toolbar','figure','Position',[StartXPos,SS(4)- (Height + CG.GUI.Props.MenuOffset + CG.GUI.Props.WindowOffset),Width,Height]);
      CG.GUI.LastXPos = StartXPos + Width;
      C_addFigure(cFIG);
    end
    
    % BASIC ACCESS FUNCTIONS
    function N = getName(O);                     N = O.Name;  end
    function O = setName(O,Name);            O.Name = Name;  end
    function M = getModules(O);                 M = O.Modules;  end
    function N = getNumberOfModules(O);  N = length(O.Modules); end
    function T = getTypesOfModules(O);     for i=1:length(O.Modules) T{i} = O.Modules{i}.Type; end; end
    %=======================================
    function IDTypeModules = addModules(O,Types,Names,Pars) % ADD MULTIPLE MODULES
      IDStart = length(O.Modules);
      for iM = 1:length(Types)
        cID = IDStart + iM;
        AllTypes = {O.Modules.Type};
        cIDType = sum(strcmp(Types{iM},AllTypes))+1;
        O.Modules(cID).IDType = cIDType;
        O.Modules(cID).Type = Types{iM};
        O.Modules(cID).Name = Names{iM};
        O.Modules(cID).ID = cID;
        O.Modules(cID).ParFIG = 2000000+cID;
        if isempty(Pars{iM})
          Pars{iM} = C_getParametersModule('ModuleType',Types{iM},'DefaultPars',1);
          eval(['IDTypeModules(iM) = C_create',Types{iM},'(''Name'',Names{iM});']);
        else
           eval(['[IDTypeModules(iM),Pars{iM}] = C_create',Types{iM},'(''Name'',Names{iM},Pars{iM}{:});']);
        end
        O.Modules(cID).Parameters = Pars{iM};
        if cIDType ~= IDTypeModules(iM)
          error('ID for this Type of Module is incorrect');
        end
      end
    end
    %=======================================
    function P = getParameters(O)
      P = C_convPars2Struct(O.ParametersFull);
    end
    
    function O = setParameters(O,Pars)
      for i=1:size(O.ParametersFull,1)
        cPar = O.ParametersFull{i,1};
        if isfield(Pars,cPar)  O.ParametersFull{i,2} = Pars.(cPar); end
        O.Parameters.(cPar) = O.ParametersFull{i,2};
      end
    end
    %=======================================
    function P = getParametersModule(O,Name)
      [cModule,Ind] = getModule(O,Name);
      P = cModule.Parameters;
    end
    %=======================================
    function P = setParametersModule(O,Name,P)
      [cModule,Ind] = getModule(O,Name);
      O.Modules(Ind).Parameters = P;
    end
    %=======================================
    function [M,iM] = getModule(O,Name)
      for iM =1:length(O.Modules)
        if strcmp(Name,O.Modules(iM).Name)
          M = O.Modules(iM);
          break;
        end
      end
    end
    %=======================================
    function Name = getModuleName(O,ModuleType,IDType)
      Name = '';
      for iM =1:length(O.Modules)
        if strcmp(ModuleType,O.Modules(iM).Type) && (IDType == O.Modules(iM).IDType)
          Name = O.Modules(iM).Name;
          break;
        end
      end
    end
    %=======================================
    function increaseTrial(O)
      global CG;
      O.Trial = O.Trial + 1;
      O.setTrial(O.Trial);
    end
    %=======================================
    function O = setTrial(O,Value)
      global CG;
      O.Trial = Value;
      set(CG.GUI.Main.Trial,'String',num2str(O.Trial));
    end 
  
    %=======================================
    function setInputChannel(O,Type,DeviceID,Channel,Threshold,InputName,Save)
      global CG;
      
      if ~exist('Save','var') Save = 1; end
      
      % RECORD ATTACHMENT POINT OF THE SENSOR
      O.HW.NInputs = O.HW.NInputs + 1;  cInputInd = O.HW.NInputs;
      
      if isfield(O.HW,'Inputs')
        UsedChAI = {O.HW.Inputs.Channel};
        cInd = find(strcmp(Channel,UsedChAI));
        if ~isempty(cInd)
          error(['The analog input ',Channel,' is already in use.']);
        end
      end
      
      % RECORD SENSOR CONFIGURATION
      O.HW.Inputs(cInputInd) = ...
        struct('Type',Type,'TypeID',DeviceID,'Channel',Channel,...
        'Threshold',Threshold,'Name',InputName,'Index',cInputInd,'Save',Save);
      
      % ADD THE CHANNEL TO THE DATA ACQUISITION
      C_addChannelsNI(DeviceID,'ChannelType','AI','Channels',Channel,...
        'Threshold',Threshold,'Names',InputName);
            
      % Record the Inputindices for simple access later
      O.HW.InputIndices.(Type)(DeviceID).I = [O.HW.Inputs.Index]; 
    end
    
       %=======================================
    function setOutputChannel(O,Type,DeviceID,Channel,Name,Save)
      global CG;
      
      if ~exist('Save','var') Save = 1; end
      
      % RECORD ATTACHMENT POINT OF THE OUTPUT
      O.HW.NOutputs = O.HW.NOutputs + 1;  cOutputInd = O.HW.NOutputs;
      
      if isfield(O.HW,'Outputs')
        UsedChAO = {O.HW.Outputs.ChAOName};
        cInd = find(strcmp(Channel,UsedChAO));
        if ~isempty(cInd)
          error(['The analog output ',Channel,' is already in use.']);
        end
      end
      
      % RECORD OUTPUT CONFIGURATION
      O.HW.Outputs(cOutputInd) = ...
        struct('Type',Type,'TypeID',DeviceID,'Channel',Channel,...
        'Name',Name,'Index',cOutputInd,'Save',Save);
      
      % ADD THE CHANNEL TO THE DATA ACQUISITION
      C_addChannelsNI(DeviceID,'ChannelType','AO','Channels',Channel,'Names',Name);
            
      % Record the Outputindices for simple access later
      O.HW.OutputIndices.(Type)(DeviceID).I = [O.HW.Outputs.Index]; 
    end
    
    %=======================================
    function setDOutputChannel(O,Type,DeviceID,Channel,Name,Save)
      global CG;
      
      if ~exist('Save','var') Save = 1; end
      
      % RECORD ATTACHMENT POINT OF THE OUTPUT
      O.HW.NDOutputs = O.HW.NDOutputs + 1;  cDOutputInd = O.HW.NDOutputs;
      
      if isfield(O.HW,'Outputs')
        UsedChDO = {O.HW.DOutputs.ChDOName};
        cInd = find(strcmp(Channel,UsedChDO));
        if ~isempty(cInd)
          error(['The digital output ',Channel,' is already in use.']);
        end
      end
      
      % RECORD OUTPUT CONFIGURATION
      O.HW.DOutputs(cDOutputInd) = ...
        struct('Type',Type,'TypeID',DeviceID,'Channel',Channel,...
        'Name',Name,'Index',cDOutputInd,'Save',Save);
      
      % ADD THE CHANNEL TO THE DATA ACQUISITION
      C_addChannelsNI(DeviceID,'ChannelType','DO','Channels',Channel,'Names',Name);
            
      % Record the Outputindices for simple access later
      O.HW.DOutputIndices.(Type)(DeviceID).I = [O.HW.DOutputs.Index]; 
    end
    
    
    function InputIndex = getInputByName(O,Name)
      Ind = strcmp(Name,{O.HW.Inputs.Name});
      InputIndex = O.HW.Inputs(Ind).Index;
    end
    
    function showInputChannels(O)
      fprintf('\nDevice\tChannel\tIndex\t\tName\n------------------------------------------------------\n');
      for i=1:length(O.HW.Inputs)
        fprintf([O.HW.Inputs(i).Type,' ',num2str(O.HW.Inputs(i).TypeID),'\t\t',...
          O.HW.Inputs(i).Channel,'\t\t\t',num2str(O.HW.Inputs(i).Index),' \t\t\t',O.HW.Inputs(i).Name,'\n']);
      end
    
    end    
  end
end